
#include <Ogre.h>
#include <OgreDefaultHardwareBufferManager.h>
#include <fstream>

using namespace Ogre;
using namespace std;

#define MIN(a, b) a < b ? a : b

inline void ReadData(HardwareBuffer * hardwareBuffer, void * buffer)
{
	unsigned int times = hardwareBuffer->getSizeInBytes() / 1048576 + 1;
	for (unsigned int i = 0; i < times; i++)
		hardwareBuffer->readData(i * 1048576, MIN(hardwareBuffer->getSizeInBytes() - i * 1048576, 1048576), buffer);
}

int main(int argc, char * argv[])
{
	//TODO: Add shadow vertices support!!!!!!!!!!
	if (argc < 2)
		return 1;

	LogManager * logManager = new LogManager();
	logManager->createLog("Log.txt", true);
	Log::Stream log = logManager->stream();
	ResourceGroupManager * rgm = new ResourceGroupManager();
	LodStrategyManager * lodManager = new LodStrategyManager();
	MeshManager * meshManager = new MeshManager();
	DefaultHardwareBufferManager * bufferManager = new DefaultHardwareBufferManager();
	MeshSerializer * serializer = new MeshSerializer();

	const char * fname = argv[1];
	std::ifstream ifs;
	ifs.open(fname, std::ios_base::in | std::ios_base::binary);

	if (!ifs.good())
	{
		log << "Unable to open " << fname << Log::Stream::Flush();
		return 1;
	}

	MeshPtr mesh = meshManager->create("Mesh32", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	DataStreamPtr stream(new FileStreamDataStream(&ifs, false));
	serializer->importMesh(stream, mesh.getPointer());
	bool hasSharedVertices = mesh->sharedVertexData != 0;
	bool isShared16bit = false;
	if (hasSharedVertices)
	{
		log << "Mesh has " << mesh->sharedVertexData->vertexCount << " vertices" << Log::Stream::Flush();
		isShared16bit = mesh->sharedVertexData->vertexCount < 65536;
	}
	log << mesh->getNumSubMeshes() << " submeshes have been found" << Log::Stream::Flush();
	SubMesh * submesh;
	HardwareIndexBufferSharedPtr indexBuffer, newIndexBuffer;
	int current32index;
	short current16index;
	bool shouldExport = false;
	unsigned short numDeletedSubMeshes = 0, numSubMeshes = mesh->getNumSubMeshes();
	for (unsigned short i = 0; i < numSubMeshes; i++)
	{
		submesh = mesh->getSubMesh(i);
		indexBuffer = submesh->indexData->indexBuffer;
		if (indexBuffer->getType() == HardwareIndexBuffer::IT_32BIT)
		{
			log << "Submesh #" << i + numDeletedSubMeshes << " has 32bit indices array" << Log::Stream::Flush();
			if (submesh->useSharedVertices)
			{
				log << "Submesh #" << i + numDeletedSubMeshes << " shares vertices" << Log::Stream::Flush();

				if (isShared16bit)
				{
					newIndexBuffer = bufferManager->createIndexBuffer(HardwareIndexBuffer::IT_16BIT, indexBuffer->getNumIndexes(), indexBuffer->getUsage());
					for (unsigned int j = 0; j < indexBuffer->getNumIndexes(); j++)
					{
						indexBuffer->readData(j * 4, 4, &current32index);
						current16index = current32index;
						newIndexBuffer->writeData(j * 2, 2, &current16index);
					}
					submesh->indexData->indexBuffer = newIndexBuffer;
					shouldExport = true;
				}
			}
			else
			{
				log << "Submesh #" << i + numDeletedSubMeshes << " has " << submesh->vertexData->vertexCount << " vertices" << Log::Stream::Flush();

				unsigned int * indices = new unsigned int[indexBuffer->getNumIndexes()];
				ReadData(indexBuffer.getPointer(), indices);
				std::map<unsigned int, std::vector<unsigned int>> indicesMap;
				unsigned int indexOffset, indexCount;
				SubMesh * newSubmesh;
				HardwareIndexBufferSharedPtr newIB;
				VertexDeclaration * newVD;
				VertexBufferBinding * newVBB;
				HardwareVertexBufferSharedPtr newVB, vertexBuffer;
				const VertexElement * element;
				unsigned int maxVertices = 65536;
				if (submesh->operationType == RenderOperation::OT_TRIANGLE_LIST)
					maxVertices = 65535;

				for (indexOffset = 0; indexOffset < indexBuffer->getNumIndexes();)
				{
					indexCount = 0;
					indicesMap.clear();

					if (indexOffset)
					{
						switch (submesh->operationType)
						{
						case RenderOperation::OT_TRIANGLE_FAN:
							indicesMap[indices[0]].push_back(indicesMap.size());
							indexCount++;
						case RenderOperation::OT_LINE_STRIP:
							indexOffset--;
							break;
						case RenderOperation::OT_TRIANGLE_STRIP:
							indexOffset-=2;
							break;
						}
					}

					while (indicesMap.size() < maxVertices && indexOffset < indexBuffer->getNumIndexes())
					{
						indicesMap[indices[indexOffset]].push_back(indicesMap.size());
						indexOffset++;
						indexCount++;
					}
					newSubmesh = mesh->createSubMesh();

					newIB = bufferManager->createIndexBuffer(HardwareIndexBuffer::IT_16BIT, indexCount, submesh->indexData->indexBuffer->getUsage());
					unsigned short index = 0;
					for (std::map<unsigned int, std::vector<unsigned int>>::iterator it = indicesMap.begin(); it != indicesMap.end(); it++, index++)
						for (std::vector<unsigned int>::iterator it2 = it->second.begin(); it2 != it->second.end(); it2++)
							newIB->writeData(*it2 * newIB->getIndexSize(), newIB->getIndexSize(), &index);

					newSubmesh->indexData = new IndexData();
					newSubmesh->indexData->indexStart = 0;
					newSubmesh->indexData->indexCount = indexCount;
					newSubmesh->indexData->indexBuffer = newIB;
				

					newVD = bufferManager->createVertexDeclaration();
					for (unsigned int elementIndex = 0; elementIndex < submesh->vertexData->vertexDeclaration->getElementCount(); elementIndex++)
					{
						element = submesh->vertexData->vertexDeclaration->getElement(elementIndex);
						newVD->addElement(element->getSource(), element->getOffset(), element->getType(), element->getSemantic(), element->getIndex());
					}
					newVBB = bufferManager->createVertexBufferBinding();
					for (unsigned int bufferIndex = 0; bufferIndex < submesh->vertexData->vertexBufferBinding->getBufferCount(); bufferIndex++)
					{
						vertexBuffer = submesh->vertexData->vertexBufferBinding->getBuffer(bufferIndex);
						newVB = bufferManager->createVertexBuffer(vertexBuffer->getVertexSize(), indicesMap.size(), vertexBuffer->getUsage());
						char * data  = new char[vertexBuffer->getVertexSize()];
						index = 0;
						for (std::map<unsigned int, std::vector<unsigned int>>::iterator it = indicesMap.begin(); it != indicesMap.end(); it++, index++)
						{
							vertexBuffer->readData(it->first * vertexBuffer->getVertexSize(), vertexBuffer->getVertexSize(), data);
							newVB->writeData(index * vertexBuffer->getVertexSize(), vertexBuffer->getVertexSize(), data);
						}
						delete[] data;
						newVBB->setBinding(bufferIndex, newVB);
					}
					newSubmesh->vertexData = new VertexData(newVD, newVBB);
					newSubmesh->vertexData->vertexStart = 0;
					newSubmesh->vertexData->vertexCount = indicesMap.size();

					newSubmesh->setMaterialName(submesh->getMaterialName());
					newSubmesh->operationType = submesh->operationType;
					newSubmesh->useSharedVertices = false;
					newSubmesh->setBuildEdgesEnabled(submesh->isBuildEdgesEnabled());
				}
				delete[] indices;
				mesh->destroySubMesh(i);
				i--;
				numDeletedSubMeshes++;
				numSubMeshes--;
				shouldExport = true;
			}
		}
		else
			log << "Submesh #" << i + numDeletedSubMeshes << " has 16bit indices array" << Log::Stream::Flush();
	}

	stream.setNull();
	ifs.close();

	if (shouldExport)
	{
		std::string exportName = fname;
		int index = exportName.find_last_of('.');
		exportName = exportName.insert(index, "16");
		serializer->exportMesh(mesh.getPointer(), exportName, MESH_VERSION_LATEST);
	}

	delete serializer;
	delete bufferManager;
	delete meshManager;
	delete lodManager;
	delete rgm;
	delete logManager;
	return 0;
}